package com.yuxl.admin.common.security.service;

import com.alibaba.fastjson2.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yuxl.admin.common.security.config.JwtProperties;
import com.yuxl.admin.common.security.domain.AuthUserDetails;
import com.yuxl.admin.manager.auth.core.entity.*;
import com.yuxl.admin.manager.auth.core.service.AuthRoleService;
import com.yuxl.admin.manager.auth.core.service.AuthUserService;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.jackson.io.JacksonSerializer;
import io.jsonwebtoken.security.Keys;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.security.Key;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;


@Slf4j
@Service
@RequiredArgsConstructor
public class JwtService {

    private final ReactiveAuthenticationManager reactiveAuthenticationManager;
    private final JwtProperties jwtProperties;
    private final CustomUserDetailsService customUserDetailsService;
    private final AuthUserService authUserService;
    private final AuthRoleService authRoleService;
    private final RedisTokenService redisTokenService;
    private final ObjectMapper objectMapper;

    /**
     * 获取用于签名JWT的密钥
     * @return 用于签名的Key对象
     */
    private Key getSigningKey() {
        byte[] keyBytes = jwtProperties.getSecret().getBytes();
        // 使用密钥字节数组创建HMAC-SHA密钥
        return Keys.hmacShaKeyFor(keyBytes);
    }



    /**
     * 验证用户凭据
     * @param username 用户名
     * @param password 密码
     * @return 认证是否成功的Mono<Boolean>
     */
    public Mono<Boolean> authenticateUser(String username, String password) {
        return reactiveAuthenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password))
                .map(auth -> true)
                .onErrorResume(e -> {
                    log.error("用户 {} 认证失败", username, e);
                    return Mono.just(false);
                });
    }

    /**
     * 生成新的JWT令牌
     * @param username 用户名
     * @return 包含新生成JWT令牌的Map
     */
    public Mono<Map<String, String>> generateNewToken(String username) {
        return customUserDetailsService.findByUsername(username)
                .flatMap(userDetails ->
                    findDetailsByUsername(username)
                        .map(info ->
                                {
                                    String token = generateToken(userDetails, info.getUser(), info.getRoles(),info.getMenus(),info.getOperates(),info.getInters());
                                    log.debug("为用户 {} 生成新的令牌", username);
                                    return token;
                                }
                        )
                )
                .flatMap(token ->
                    redisTokenService.saveToken(username, token)
                        .thenReturn(Map.of(jwtProperties.getHeaderKey(), jwtProperties.getBearerToken() + token))
                );
    }

    public String createToken(AuthUserDetails user, String subject) {
        return Jwts.builder()
                .setClaims(JSONObject.parseObject(JSONObject.toJSONString(user),Map.class))
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + jwtProperties.getExpiration() * 1000))
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .serializeToJsonWith(new JacksonSerializer<>(objectMapper))
                .compact();
    }

    /**
     * 验证JWT令牌的有效性
     * @param token 要验证的JWT令牌
     * @return 如果令牌有效返回true，否则返回false
     */
    public Boolean validateToken(String token) {
        try {
            // 尝试解析JWT令牌
            Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token);
            log.debug("JWT令牌验证成功");
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            // 如果解析失败，记录警告日志
            log.warn("JWT令牌验证失败: {}", e.getMessage());
            return false;
        }
    }

    /**
     * 从JWT令牌中提取用户名
     * @param token JWT令牌
     * @return 提取的用户名
     */
    public String extractUsername(String token) {
        String username = extractClaim(token, Claims::getSubject);
        log.debug("从JWT令牌中提取的用户名: {}", username);
        return username;
    }

    /**
     * 从JWT令牌中提取过期时间
     * @param token JWT令牌
     * @return 令牌的过期时间
     */
    public Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

    /**
     * 从JWT令牌中提取指定的声明
     * @param token JWT令牌
     * @param claimsResolver 用于解析声明的函数
     * @return 提取的声明值
     */
    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }

    /**
     * 从JWT令牌中提取所有声明
     * @param token JWT令牌
     * @return 包含所有声明的Claims对象
     */
    private Claims extractAllClaims(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    /**
     * claims转Details
     * @param claims
     * @return
     */
    private AuthUserDetails claimsToDetails(Claims claims) {
        return JSONObject.parseObject(JSONObject.toJSONString(claims),AuthUserDetails.class);
    }

    /**
     * 检查JWT令牌是否已过期
     * @param token JWT令牌
     * @return 如果令牌已过期返回true，否则返回false
     */
    private Boolean isTokenExpired(String token) {
        Date expirationDate = extractExpiration(token);
        boolean isExpired = expirationDate.before(new Date());
        if (isExpired) {
            log.debug("JWT令牌已过期");
        }
        return isExpired;
    }

    /**
     * 验证JWT令牌对于指定用户是否有效
     * @param token JWT令牌
     * @param username 要验证的用户名
     * @return 如果令牌对于指定用户有效返回true，否则返回false
     */
    public Mono<Boolean> validateToken(String token, String username) {
        return Mono.fromCallable(() -> {
            final String tokenUsername = extractUsername(token);
            // 检查令牌中的用户名是否匹配且令牌未过期
            boolean isValid = (tokenUsername.equals(username) && !isTokenExpired(token));
            if (isValid) {
                log.debug("JWT令牌验证成功，属于用户: {}", username);
            } else {
                log.warn("JWT令牌验证失败，用户名不匹配或令牌已过期");
            }
            return isValid;
        });
    }

    /**
     * 获取当前已登录用户存入JWT中的信息
     * @return 包含用户信息的Mono<Map>
     */
    public Mono<AuthUserDetails> getCurrentUserInfo() {
        return ReactiveSecurityContextHolder.getContext()
            .flatMap(securityContext -> {
                Authentication authentication = securityContext.getAuthentication();
                if (authentication != null && authentication.isAuthenticated()) {
                    String username = authentication.getName();
                    Object credentials = authentication.getCredentials();
                    if (credentials instanceof String) {
                        String token = (String) credentials;
                        try {
                            Claims claims = extractAllClaims(token);
                            AuthUserDetails userDetails = claimsToDetails(claims);
                            log.debug("从JWT令牌中提取的用户信息: {}", userDetails);
                            return Mono.just(userDetails);
                        } catch (JwtException e) {
                            log.error("从JWT令牌中提取用户信息时发生错误", e);
                            return Mono.error(e);
                        }
                    } else {
                        log.warn("无法从认证对象中提取JWT令牌");
                        return Mono.error(new IllegalStateException("无法从认证对象中提取JWT令牌"));
                    }
                } else {
                    log.warn("当前用户未认证");
                    return Mono.error(new IllegalStateException("当前用户未认证"));
                }
            })
            .doOnError(e -> log.error("获取当前用户信息时发生错误", e))
            .onErrorResume(e -> Mono.just(new AuthUserDetails()));
    }

    /**
     * 从Authentication对象中提取JWT令牌
     * @param authentication 认证对象
     * @return JWT令牌字符串，如果无法提取则返回null
     */
    private String extractTokenFromAuthentication(Authentication authentication) {
        if (authentication.getDetails() instanceof String) {
            return (String) authentication.getDetails();
        }
        log.warn("无法从Authentication对象中提取JWT令牌");
        return null;
    }

    /**
     * 为用户生成新的JWT令牌
     * @param userDetails 用户详情
     * @return 生成的JWT令牌字符串
     */
    public String generateToken(UserDetails userDetails) {
        AuthUserDetails claims = new AuthUserDetails();
        // 创建包含用户名的令牌
        String token = createToken(claims, userDetails.getUsername());
        log.debug("为用户 {} 生成了新的JWT令牌", userDetails.getUsername());
        return token;
    }

    /**
     * 为用户生成新的JWT令牌，包含额外的用户信息
     * @param userDetails 用户详情
     * @param user 用户实体
     * @param roles 用户角色列表
     * @return 生成的JWT令牌字符串
     */
    public String generateToken(UserDetails userDetails, AuthUser user, List<AuthRole> roles, List<AuthMenu> menus, List<AuthOperate> operates, List<AuthInterface> inters) {
        AuthUserDetails claims = new AuthUserDetails();
        claims.setUser(user);
        claims.setRoles(roles);
        claims.setMenus(menus);
        claims.setOperates(operates);
        claims.setInters(inters);
        return createToken(claims, userDetails.getUsername());
    }

    public List<AuthRole> extractRoles(String token) {
        AuthUserDetails details = claimsToDetails(extractAllClaims(token));
        return details.getRoles();
    }

    public Mono<AuthUserDetails> findDetailsByUsername(String username) {
        // 查询 AuthUserDetails
        return authUserService.findByUsername(username).flatMap(user ->
            authUserService.findRolesByUserId(user.getId()).collectList().flatMap(roles -> {
                Set<Long> roleIds = roles.stream()
                        .map(AuthRole::getId)
                        .collect(Collectors.toSet());
                return Mono.zip(
                        authRoleService.getMenusByRoleIds(roleIds).collectList(),
                        authRoleService.getOperatesByRoleIds(roleIds).collectList(),
                        authRoleService.getInterfacesByRoleIds(roleIds).collectList()
                ).flatMap(tuple -> {
                        AuthUserDetails userDetails = new AuthUserDetails();
                        userDetails.setUser(user);
                        userDetails.setRoles(roles);
                        userDetails.setMenus(tuple.getT1());
                        userDetails.setOperates(tuple.getT2());
                        userDetails.setInters(tuple.getT3());
                        return Mono.just(userDetails);
                });
            })
        );
    }
}
